package com.okilo.online.app.base.popup

import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.transition.Transition
import android.view.Gravity
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver.OnGlobalLayoutListener
import android.view.WindowManager
import android.widget.PopupWindow
import androidx.annotation.ColorInt
import androidx.annotation.FloatRange
import androidx.annotation.IdRes
import androidx.annotation.LayoutRes
import androidx.annotation.StyleRes
import androidx.core.graphics.drawable.toDrawable
import androidx.core.widget.PopupWindowCompat

/**
 * Created by zyyoona7 on 2017/8/3.
 *
 *
 * PopupWindow封装
 */
abstract class OkiloBasePopup<T : OkiloBasePopup<T>> : PopupWindow.OnDismissListener {

    private companion object {
        private const val TAG = "EasyPopup"
        private const val DEFAULT_DIM = 0.7f
    }

    private var popupWindow: PopupWindow? = null
    private var context: Context? = null
    private var contentView: View? = null
    private var layoutId: Int = 0

    private var focusable: Boolean = true
    private var outsideTouchable: Boolean = true
    private var focusAndOutsideEnable: Boolean = true

    private var width: Int = ViewGroup.LayoutParams.WRAP_CONTENT
    private var height: Int = ViewGroup.LayoutParams.WRAP_CONTENT

    private var animationStyle: Int = 0
    private var onDismissListener: PopupWindow.OnDismissListener? = null

    // 背景变暗相关
    private var isBackgroundDim: Boolean = false
    private var dimValue: Float = DEFAULT_DIM
    private var dimColor: Int = Color.BLACK
    private var dimView: ViewGroup? = null

    // 过渡动画
    private var enterTransition: Transition? = null
    private var exitTransition: Transition? = null

    // 定位相关
    private var anchorView: View? = null
    private var yGravity: Int = YGravity.BELOW
    private var xGravity: Int = XGravity.LEFT
    private var offsetX: Int = 0
    private var offsetY: Int = 0

    // 输入法相关
    private var inputMethodMode: Int = PopupWindow.INPUT_METHOD_FROM_FOCUSABLE
    private var softInputMode: Int = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED

    // 测量相关
    private var isNeedReMeasureWH: Boolean = false
    private var isRealWHAlready: Boolean = false
    private var isAtAnchorViewMethod: Boolean = false
    private var onRealWHAlreadyListener: OnRealWHAlreadyListener? = null

    @Suppress("UNCHECKED_CAST")
    protected fun self(): T = this as T

    fun apply(): T {
        if (popupWindow == null) {
            popupWindow = PopupWindow()
        }

        onPopupWindowCreated()
        initContentViewAndWH()
        onPopupWindowViewCreated(contentView!!)

        if (animationStyle != 0) {
            popupWindow?.animationStyle = animationStyle
        }

        initFocusAndBack()
        popupWindow?.setOnDismissListener(this)

        enterTransition?.let { popupWindow?.enterTransition = it }
        exitTransition?.let { popupWindow?.exitTransition = it }

        return self()
    }

    private fun initContentViewAndWH() {
        if (contentView == null) {
            if (layoutId != 0 && context != null) {
                contentView = LayoutInflater.from(context).inflate(layoutId, null)
            } else {
                throw IllegalArgumentException("The content view is null, layoutId=$layoutId, context=$context")
            }
        }
        popupWindow?.contentView = contentView

        popupWindow?.width = if (width > 0 || width == ViewGroup.LayoutParams.WRAP_CONTENT || width == ViewGroup.LayoutParams.MATCH_PARENT) {
            width
        } else {
            ViewGroup.LayoutParams.WRAP_CONTENT
        }

        popupWindow?.height = if (height > 0 || height == ViewGroup.LayoutParams.WRAP_CONTENT || height == ViewGroup.LayoutParams.MATCH_PARENT) {
            height
        } else {
            ViewGroup.LayoutParams.WRAP_CONTENT
        }

        measureContentView()
        registerOnGlobalLayoutListener()

        popupWindow?.inputMethodMode = inputMethodMode
        popupWindow?.softInputMode = softInputMode
    }

    @SuppressLint("ClickableViewAccessibility")
    private fun initFocusAndBack() {
        if (!focusAndOutsideEnable) {
            popupWindow?.apply {
                isFocusable = true
                isOutsideTouchable = false
                setBackgroundDrawable(null)

                contentView?.apply {
                    isFocusable = true
                    isFocusableInTouchMode = true
                    setOnKeyListener { _, keyCode, event ->
                        if (keyCode == KeyEvent.KEYCODE_BACK) {
                            dismiss()
                            true
                        } else {
                            false
                        }
                    }
                }

                setTouchInterceptor { _, event ->
                    val x = event.x.toInt()
                    val y = event.y.toInt()

                    when {
                        event.action == MotionEvent.ACTION_DOWN && (x < 0 || x >= width || y < 0 || y >= height) -> true
                        event.action == MotionEvent.ACTION_OUTSIDE -> true
                        else -> false
                    }
                }
            }
        } else {
            popupWindow?.apply {
                isFocusable = focusable
                isOutsideTouchable = outsideTouchable
                setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
            }
        }
    }

    /**** 自定义生命周期方法 ****/
    protected open fun onPopupWindowCreated() {
        initAttributes()
    }

    protected open fun onPopupWindowViewCreated(contentView: View) {
        initViews(contentView, self())
    }

    protected open fun onPopupWindowDismiss() {
        // 子类可重写
    }

    protected abstract fun initAttributes()
    protected abstract fun initViews(view: View, popup: T)

    private fun measureContentView() {
        val contentView = getContentView() ?: return

        if (width <= 0 || height <= 0) {
            contentView.measure(0, View.MeasureSpec.UNSPECIFIED)
            if (width <= 0) width = contentView.measuredWidth
            if (height <= 0) height = contentView.measuredHeight
        }
    }

    private fun registerOnGlobalLayoutListener() {
        getContentView()?.viewTreeObserver?.addOnGlobalLayoutListener(object : OnGlobalLayoutListener {
            override fun onGlobalLayout() {
                getContentView()?.viewTreeObserver?.removeOnGlobalLayoutListener(this)
                width = getContentView()?.width ?: width
                height = getContentView()?.height ?: height

                isRealWHAlready = true
                isNeedReMeasureWH = false

                onRealWHAlreadyListener?.onRealWHAlready(
                    this@OkiloBasePopup,
                    width,
                    height,
                    anchorView?.width ?: 0,
                    anchorView?.height ?: 0
                )

                if (isShowing() && isAtAnchorViewMethod) {
                    updateLocation(width, height, anchorView!!, yGravity, xGravity, offsetX, offsetY)
                }
            }
        })
    }

    private fun updateLocation(width: Int, height: Int, anchor: View, yGravity: Int, xGravity: Int, x: Int, y: Int) {
        val calculatedX = calculateX(anchor, xGravity, width, x)
        val calculatedY = calculateY(anchor, yGravity, height, y)
        popupWindow?.update(anchor, calculatedX, calculatedY, width, height)
    }

    /**** 设置属性方法 ****/
    fun setContext(context: Context): T {
        this.context = context
        return self()
    }

    fun setContentView(contentView: View): T {
        this.contentView = contentView
        this.layoutId = 0
        return self()
    }
    fun setContentView(@LayoutRes layoutId: Int): T {
        this.contentView = null
        this.layoutId = layoutId
        return self()
    }
    fun setContentView(context: Context, @LayoutRes layoutId: Int): T {
        this.context = context
        this.contentView = null
        this.layoutId = layoutId
        return self()
    }
    fun setWidth(width: Int): T {
        this.width = width
        return self()
    }
    fun setHeight(height: Int): T {
        this.height = height
        return self()
    }
    fun setAnchorView(view: View): T {
        this.anchorView = view
        return self()
    }
    fun setYGravity(@YGravity yGravity: Int): T {
        this.yGravity = yGravity
        return self()
    }
    fun setXGravity(@XGravity xGravity: Int): T {
        this.xGravity = xGravity
        return self()
    }
    fun setOffsetX(offsetX: Int): T {
        this.offsetX = offsetX
        return self()
    }
    fun setOffsetY(offsetY: Int): T {
        this.offsetY = offsetY
        return self()
    }
    fun setAnimationStyle(@StyleRes animationStyle: Int): T {
        this.animationStyle = animationStyle
        return self()
    }
    fun setFocusable(focusable: Boolean): T {
        this.focusable = focusable
        return self()
    }
    fun setOutsideTouchable(outsideTouchable: Boolean): T {
        this.outsideTouchable = outsideTouchable
        return self()
    }
    fun setFocusAndOutsideEnable(focusAndOutsideEnable: Boolean): T {
        this.focusAndOutsideEnable = focusAndOutsideEnable
        return self()
    }
    fun setBackgroundDimEnable(isDim: Boolean): T {
        this.isBackgroundDim = isDim
        return self()
    }
    fun setDimValue(@FloatRange(from = 0.0, to = 1.0) dimValue: Float): T {
        this.dimValue = dimValue
        return self()
    }
    fun setDimColor(@ColorInt color: Int): T {
        this.dimColor = color
        return self()
    }
    fun setDimView(dimView: ViewGroup): T {
        this.dimView = dimView
        return self()
    }
    fun setInputMethodMode(mode: Int): T {
        this.inputMethodMode = mode
        return self()
    }
    fun setSoftInputMode(mode: Int): T {
        this.softInputMode = mode
        return self()
    }
    fun setNeedReMeasureWH(needReMeasureWH: Boolean): T {
        this.isNeedReMeasureWH = needReMeasureWH
        return self()
    }
    fun setOnDismissListener(listener: PopupWindow.OnDismissListener): T {
        this.onDismissListener = listener
        return self()
    }
    fun setOnRealWHAlreadyListener(listener: OnRealWHAlreadyListener): T {
        this.onRealWHAlreadyListener = listener
        return self()
    }

    fun setEnterTransition(enterTransition: Transition): T {
        this.enterTransition = enterTransition
        return self()
    }

    fun setExitTransition(exitTransition: Transition): T {
        this.exitTransition = exitTransition
        return self()
    }

    private fun checkIsApply(isAtAnchorView: Boolean) {
        if (this.isAtAnchorViewMethod != isAtAnchorView) {
            this.isAtAnchorViewMethod = isAtAnchorView
        }
        if (popupWindow == null) {
            apply()
        }
    }

    /**** 显示方法 ****/
    fun showAsDropDown() {
        anchorView?.let { showAsDropDown(it, offsetX, offsetY) }
    }

    fun showAsDropDown(anchor: View, offsetX: Int, offsetY: Int) {
        checkIsApply(false)
        handleBackgroundDim()
        anchorView = anchor
        this.offsetX = offsetX
        this.offsetY = offsetY
        if (isNeedReMeasureWH) registerOnGlobalLayoutListener()
        popupWindow?.showAsDropDown(anchor, offsetX, offsetY)
    }

    fun showAsDropDown(anchor: View) {
        checkIsApply(false)
        handleBackgroundDim()
        anchorView = anchor
        if (isNeedReMeasureWH) registerOnGlobalLayoutListener()
        popupWindow?.showAsDropDown(anchor)
    }

    fun showAsDropDown(anchor: View, offsetX: Int, offsetY: Int, gravity: Int) {
        checkIsApply(false)
        handleBackgroundDim()
        anchorView = anchor
        this.offsetX = offsetX
        this.offsetY = offsetY
        if (isNeedReMeasureWH) registerOnGlobalLayoutListener()
        popupWindow?.let { PopupWindowCompat.showAsDropDown(it, anchor, offsetX, offsetY, gravity) }
    }

    fun showAtLocation(parent: View, gravity: Int, offsetX: Int, offsetY: Int) {
        checkIsApply(false)
        handleBackgroundDim()
        anchorView = parent
        this.offsetX = offsetX
        this.offsetY = offsetY
        if (isNeedReMeasureWH) registerOnGlobalLayoutListener()
        popupWindow?.showAtLocation(parent, gravity, offsetX, offsetY)
    }

    fun showAtAnchorView() {
        anchorView?.let { showAtAnchorView(it, yGravity, xGravity) }
    }

    fun showAtAnchorView(anchor: View, vertGravity: Int, horizGravity: Int) {
        showAtAnchorView(anchor, vertGravity, horizGravity, 0, 0)
    }

    fun showAtAnchorView(anchor: View, vertGravity: Int, horizGravity: Int, x: Int, y: Int) {
        checkIsApply(true)
        anchorView = anchor
        offsetX = x
        offsetY = y
        yGravity = vertGravity
        xGravity = horizGravity
        handleBackgroundDim()

        val calculatedX = calculateX(anchor, horizGravity, width, offsetX)
        val calculatedY = calculateY(anchor, vertGravity, height, offsetY)

        if (isNeedReMeasureWH) registerOnGlobalLayoutListener()
        popupWindow?.let { PopupWindowCompat.showAsDropDown(it, anchor, calculatedX, calculatedY, Gravity.NO_GRAVITY) }
    }

    private fun calculateY(anchor: View, vertGravity: Int, measuredH: Int, y: Int): Int = when (vertGravity) {
        YGravity.ABOVE -> y - measuredH - anchor.height
        YGravity.ALIGN_BOTTOM -> y - measuredH
        YGravity.CENTER -> y - anchor.height / 2 - measuredH / 2
        YGravity.ALIGN_TOP -> y - anchor.height
        else -> y
    }

    private fun calculateX(anchor: View, horizGravity: Int, measuredW: Int, x: Int): Int = when (horizGravity) {
        XGravity.LEFT -> x - measuredW
        XGravity.ALIGN_RIGHT -> x - measuredW + anchor.width
        XGravity.CENTER -> x + anchor.width / 2 - measuredW / 2
        XGravity.RIGHT -> x + anchor.width
        else -> x
    }

    /**** 其他方法 ****/
    fun getContentView(): View? = popupWindow?.contentView
    fun getPopupWindow(): PopupWindow? = popupWindow
    fun getWidth(): Int = width
    fun getHeight(): Int = height
    fun getXGravity(): Int = xGravity
    fun getYGravity(): Int = yGravity
    fun getOffsetX(): Int = offsetX
    fun getOffsetY(): Int = offsetY
    fun isShowing(): Boolean = popupWindow?.isShowing == true
    fun isRealWHAlready(): Boolean = isRealWHAlready

    @Suppress("UNCHECKED_CAST")
    fun <V : View> findViewById(@IdRes viewId: Int): V? {
        return getContentView()?.findViewById(viewId) as? V
    }

    fun dismiss() {
        popupWindow?.dismiss()
    }

    override fun onDismiss() {
        handleDismiss()
    }

    private fun handleDismiss() {
        onDismissListener?.onDismiss()
        clearBackgroundDim()
        popupWindow?.takeIf { it.isShowing }?.dismiss()
        onPopupWindowDismiss()
    }

    private fun handleBackgroundDim() {
        if (!isBackgroundDim) return

        dimView?.let { applyDim(it) } ?: run {
            (getContentView()?.context as? Activity)?.let { applyDim(it) }
        }
    }

    private fun applyDim(activity: Activity) {
        val parent = activity.window.decorView.rootView as? ViewGroup ?: return
        applyDimToViewGroup(parent)
    }

    private fun applyDim(viewGroup: ViewGroup) {
        applyDimToViewGroup(viewGroup)
    }

    private fun applyDimToViewGroup(parent: ViewGroup) {
        val dimDrawable = dimColor.toDrawable().apply {
            setBounds(0, 0, parent.width, parent.height)
            alpha = (255 * dimValue).toInt()
        }
        parent.overlay.add(dimDrawable)
    }

    private fun clearBackgroundDim() {
        if (!isBackgroundDim) return

        dimView?.let { clearDim(it) } ?: run {
            (getContentView()?.context as? Activity)?.let { clearDim(it) }
        }
    }

    private fun clearDim(activity: Activity) {
        (activity.window.decorView.rootView as? ViewGroup)?.overlay?.clear()
    }

    private fun clearDim(viewGroup: ViewGroup) {
        viewGroup.overlay.clear()
    }

    interface OnRealWHAlreadyListener {
        fun onRealWHAlready(basePopup: OkiloBasePopup<*>, popWidth: Int, popHeight: Int, anchorW: Int, anchorH: Int)
    }
}
